/* ip.c */

#include <string.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define USE_GETIFADDRS  1

#if USE_GETIFADDRS
#include <ifaddrs.h>
#else
#include <sys/ioctl.h>
#include <net/if.h>
#endif

#include "ip.h"

/**
 * Convert "A.B.C.D" string to an IP address.
 */
int ip_from_string(const char *str, net_u32 *ip)
{
  struct in_addr addr;
  if (! inet_aton(str, &addr))
    return -1;

  *ip = ntohl(addr.s_addr);
  return 0;
}

/**
 * Set socket to non-blocking mode.
 */
int ip_set_nonblock(int sock)
{
  int flags;

  flags = fcntl(sock, F_GETFL, 0);
  if (flags == -1)
    return -1;
  
  if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1)
    return -1;

  return 0;
}

#if USE_GETIFADDRS

net_u32 ip_guess_local_address(net_u32 hint)
{
  struct ifaddrs *list, *ifa;
  net_u32 best_match;
  
  if (getifaddrs(&list) < 0)
    return 0;

  best_match = 0;
  for (ifa = list; ifa != NULL; ifa = ifa->ifa_next) {
    net_u32 ip;

    //printf("%s: %d\n", ifa->ifa_name, ifa->ifa_addr->sa_family);

    /* ignore non-ipv4 addresses */
    if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET)
      continue;

    /* ignore local interfaces */
    if (memcmp(ifa->ifa_name, "lo", 2) == 0 && (ifa->ifa_name[2] == 0 || ifa->ifa_name[2] == '0'))
      continue;

    if (ifa->ifa_addr == NULL)
      continue;
    ip = htonl(((struct sockaddr_in *) ifa->ifa_addr)->sin_addr.s_addr);

    /* if we have no hint, return first good interface */
    if (hint == 0)
      return ip;

    /* if interface has a netmask and the hint is inside it, return it */
    if (ifa->ifa_netmask != NULL) {
      net_u32 netmask = htonl(((struct sockaddr_in *) ifa->ifa_netmask)->sin_addr.s_addr);
      if ((hint & netmask) == (ip & netmask))
        return ip;
    }

    /* check if the current is better than the best */
    if (best_match == 0)
      best_match = ip;
    else {
      unsigned int d_best = (best_match > hint) ? best_match - hint : hint - best_match;
      unsigned int d_ip = (ip > hint) ? ip - hint : hint - ip;

      if (d_best > d_ip)
        best_match = ip;
    }
  }

  freeifaddrs(list);
  return best_match;
}

#else

net_u32 ip_guess_local_address(net_u32 hint)
{
  struct ifreq reqs[10];
  struct ifconf conf;
  net_u32 best_match;
  int sock, i;

  conf.ifc_len = sizeof(reqs);
  conf.ifc_req = reqs;

  /* get interface information */
  sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    return 0;
  if (ioctl(sock, SIOCGIFCONF, &conf) < 0) {
    close(sock);
    return 0;
  }
  close(sock);

  /* search for best match based on hint */
  best_match = 0;
  for (i = 0; i < conf.ifc_len/sizeof(struct ifreq); i++) {
    net_u32 ip;
    const char *name;

    if (reqs[i].ifr_addr.sa_family != AF_INET)
      continue;
    name = reqs[i].ifr_name;
    ip = htonl(((struct sockaddr_in *) &reqs[i].ifr_addr)->sin_addr.s_addr);

    if (strcmp(name, "lo") == 0
        || memcmp(name, "tun", 3) == 0)
      continue;

    if (hint == 0)
      return ip;

    if (best_match == 0)
      best_match = ip;
    else {
      unsigned int d_best = (best_match > hint) ? best_match - hint : hint - best_match;
      unsigned int d_ip = (ip > hint) ? ip - hint : hint - ip;

      if (d_best > d_ip)
        best_match = ip;
    }
  }

  return best_match;
}

#endif
